///
// * jdbm LICENSE v1.00
// *
// * Redistribution and use of this software and associated documentation
// * ("Software"), with or without modification, are permitted provided
// * that the following conditions are met:
// *
// * 1. Redistributions of source code must retain copyright
// *    statements and notices.  Redistributions must also contain a
// *    copy of this document.
// *
// * 2. Redistributions in binary form must reproduce the
// *    above copyright notice, this list of conditions and the
// *    following disclaimer in the documentation and/or other
// *    materials provided with the distribution.
// *
// * 3. The name "jdbm" must not be used to endorse or promote
// *    products derived from this Software without prior written
// *    permission of Cees de Groot.  For written permission,
// *    please contact cg@cdegroot.com.
// *
// * 4. Products derived from this Software may not be called "jdbm"
// *    nor may "jdbm" appear in their names without prior written
// *    permission of Cees de Groot.
// *
// * 5. Due credit should be given to the jdbm Project
// *    (http://jdbm.sourceforge.net/).
// *
// * THIS SOFTWARE IS PROVIDED BY THE ndbm PROJECT AND CONTRIBUTORS
// * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
// * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
// * CEES DE GROOT OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// * OF THE POSSIBILITY OF SUCH DAMAGE.
// *
// * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
// * Contributions are Copyright (C) 2000 by their associated contributors.
// *
// * $Id: TransactionManager.java,v 1.7 2005/06/25 23:12:32 doomdark Exp $
// 

//*************************************************************************
//**  Included in JDMB 1.0 port to SharpDBM;  11/2013 Cyrus Neah cneah@codingreal.com
//**  SharpDBM is an independent reimplementation of the JDBM 1.0 software library in C#  
//*************************************************************************

using System.Collections;
using SharpDBM.helper;
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Collections.Generic;
using System.Runtime.Serialization;


namespace SharpDBM.recman
{


///
// *  This class manages the transaction log that belongs to every
// *  {@link RecordFile}. The transaction log is either clean, or
// *  in progress. In the latter case, the transaction manager
// *  takes care of a roll forward.
// *<p>
// *  Implementation note: this is a proof-of-concept implementation
// *  which hasn't been optimized for speed. For instance, all sorts
// *  of streams are created for every transaction.
// 
// TODO: Handle the case where we are recovering lg9 and lg0, were we
// should start with lg9 instead of lg0!

	public  class TransactionManager
	{
		private RecordFile owner;

	// streams for transaction log.
		private FileStream fos;
		//private ObjectOutputStream oos;
		private BinaryWriter oos;
        /// serializer
        BinaryFormatter bFormatter;

//    *
//     * By default, we keep 10 transactions in the log file before
//     * synchronizing it with the main database file.
//     
		internal const int DEFAULT_TXNS_IN_LOG = 10;

//    *
//     * Maximum number of transactions before the log file is
//     * synchronized with the main database file.
//     
		private int _maxTxns = DEFAULT_TXNS_IN_LOG;

//    *
//     * In-core copy of transactions. We could read everything back from
//     * the log file, but the RecordFile needs to keep the dirty blocks in
//     * core anyway, so we might as well point to them and spare us a lot
//     * of hassle.
//     
		private ArrayList[] txns = new ArrayList[DEFAULT_TXNS_IN_LOG];
		private int curTxn = -1;

	/// Extension of a log file. 
		internal const string extension = ".lg";

//    *
//     *  Instantiates a transaction manager instance. If recovery
//     *  needs to be performed, it is done.
//     *
//     *  @param owner the RecordFile instance that owns this transaction mgr.
//     

		internal TransactionManager(RecordFile owner)
		{
			this.owner = owner;
			recover();
			open();
		}


//    *
//     * Synchronize log file data with the main database file.
//     * <p>
//     * After this call, the main database file is guaranteed to be 
//     * consistent and guaranteed to be the only file needed for 
//     * backup purposes.
//     

		public virtual void synchronizeLog()
		{
			synchronizeLogFromMemory();
		}


//    *
//     * Set the maximum number of transactions to record in
//     * the log (and keep in memory) before the log is
//     * synchronized with the main database file.
//     * <p>
//     * This method must be called while there are no
//     * pending transactions in the log.
//     

		public virtual void setMaximumTransactionsInLog(int maxTxns)
		{
			if (maxTxns <= 0)
			{
				throw new ArgumentOutOfRangeException("Argument 'maxTxns' must be greater than 0.");
			}
			if (curTxn != -1)
			{
				throw new InvalidOperationException("Cannot change setting while transactions are pending in the log");
			}
			_maxTxns = maxTxns;
			txns = new ArrayList[maxTxns];
		}


	/// Builds logfile name 
		private string makeLogName()
		{
			return owner.getFileName() + extension;
		}


	/// Synchs in-core transactions to data file and opens a fresh log 

		private void synchronizeLogFromMemory()
		{
			close();

			//TreeSet<BlockIo> blockList = new TreeSet<BlockIo>();
			SortedSet<BlockIo> blockList = new SortedSet<BlockIo>(new BlockIoComparator());

			int numBlocks = 0;
			int writtenBlocks = 0;
			for (int i = 0; i < _maxTxns; i++)
			{
				if (txns[i] == null)                    
					continue;
			// Add each block to the blockList, replacing the old copy of this
			// block if necessary, thus avoiding writing the same block twice
				IEnumerator k = txns[i].GetEnumerator();

				while (k.MoveNext())
				{
					
					BlockIo block = (BlockIo)k.Current;

					if (blockList != null && blockList.Count > 0)
					{
						if (blockList.Contains(block))
						{
							block.decrementTransactionCount();
						}
					}

					else
					{
						writtenBlocks++;
						bool result = blockList.Add(block);
					}
					numBlocks++;
				}

				txns[i] = null;
			}
			//c# comment remove null transactions
			//((ArrayList)txns).Remove
		   // var txns_list = new List<ArrayList>(txns);
		   // for (int i=txns_list.Count-1;i >=0;i--)
		  //  {
			   // if (txns_list[i] == null) { txns_list.RemoveAt(i); }
		  //  }
			//end c# comment

		// Write the blocks from the blockList to disk
			synchronizeBlocks(blockList.GetEnumerator(), true);

			owner.sync();
			open();
		}


	/// Opens the log file 

		private void open()
		{
            fos = new FileStream(makeLogName(), FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite,1024,FileOptions.RandomAccess);
			oos = new BinaryWriter(fos);
			
			oos.Write(Magic.LOGFILE_HEADER);
			

			oos.Flush();
			fos.Flush(true);            
			curTxn = -1;
		}

	/// Startup recovery on all files 

		private void recover()
		{
			string logName = makeLogName();
			FileInfo logFile = new FileInfo(logName);
			if (!logFile.Exists)
				return;
			if (logFile.Length == 0)
			{
				try
				{
					//logFile.Delete();
                    FileStream fs = logFile.Open(FileMode.Truncate, FileAccess.ReadWrite, FileShare.ReadWrite);
                    fs.Close();
				}
				catch (Exception except)
				{
					Console.WriteLine(except.StackTrace);

				}
				
				return;
			}

			//FileInputStream fis = new FileInputStream(logFile);
			//ObjectInputStream ois = new ObjectInputStream(fis);

			FileStream fis = logFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
			BinaryReader ois = new BinaryReader(fis);
            BinaryFormatter bFormatter = new BinaryFormatter();

			try
			{
				if (ois.ReadInt16() != Magic.LOGFILE_HEADER)
					throw new Exception("Bad magic on log file");
			}
			catch (IOException e)
			{
			// corrupted/empty logfile

				try
				{
					//logFile.Delete();
				}
				catch (Exception except)
				{
					Console.WriteLine(except.StackTrace);

				}
				
				return;
			}

			while (true)
			{
				ArrayList blocks = null;
				try
				{

					//blocks = (ArrayList) ois.readObject();
                    
					//byte[] array = File.ReadAllBytes(logFile.FullName);
                    //Stream stream = File.Open(filename, FileMode.Open);

                    bFormatter = new BinaryFormatter();

                   
                    blocks = (ArrayList)bFormatter.Deserialize(fis);

                    
					
				}
				catch (TypeLoadException e)
				{
					throw new Exception("Unexcepted exception: " + e);
				}
				catch (IOException e)
				{
				// corrupted logfile, ignore rest of transactions
					break;
				}
                //SerializationException
                catch (SerializationException e)
                {
                    // corrupted logfile, ignore rest of transactions
                    break;
                }

				synchronizeBlocks(blocks.GetEnumerator(), false);

			// ObjectInputStream must match exactly each
			// ObjectOutputStream created during writes
				try
				{
					ois = new BinaryReader(fis);
                    bFormatter = new BinaryFormatter();
				}
				catch (IOException e)
				{
				// corrupted logfile, ignore rest of transactions
					break;
				}
			}
			owner.sync();

			try
			{

                
                fis.Close();
                fis = logFile.Open(FileMode.Truncate, FileAccess.ReadWrite, FileShare.ReadWrite);
                fis.Close();
                //logFile.Delete();

                
			}
			catch (Exception except)
			{
				Console.WriteLine(except.StackTrace);

			}
		}

	/// Synchronizes the indicated blocks with the owner. 

		private void synchronizeBlocks(IEnumerator blockIterator, bool fromCore)
		{
		// write block vector elements to the data file.
			while (blockIterator.MoveNext())
			{
				
				BlockIo cur = (BlockIo)blockIterator.Current;
				owner.synch(cur);
				if (fromCore)
				{
					cur.decrementTransactionCount();
					if (!cur.isInTransaction())
					{
						owner.releaseFromTransaction(cur, true);
					}
				}
			}
		}


	/// Set clean flag on the blocks. 

		private void setClean(ArrayList blocks)
		{
			
			IEnumerator k = blocks.GetEnumerator();
			while ( k.MoveNext())
			{

				
				BlockIo cur = (BlockIo)k.Current;
				
				cur.setClean();
			}
		}

	/// Discards the indicated blocks and notify the owner. 

		private void discardBlocks(ArrayList blocks)
		{
			IEnumerator k = blocks.GetEnumerator();
			while (k.MoveNext())
			{
				
				BlockIo cur = (BlockIo)k.Current;
				cur.decrementTransactionCount();
				if (!cur.isInTransaction())
				{
					owner.releaseFromTransaction(cur, false);
				}
			}
		}

//    *
//     *  Starts a transaction. This can block if all slots have been filled
//     *  with full transactions, waiting for the synchronization thread to
//     *  clean out slots.
//     

		internal virtual void start()
		{
			curTxn++;
			if (curTxn == _maxTxns)
			{
				synchronizeLogFromMemory();
				curTxn = 0;
			}
			txns[curTxn] = new ArrayList();
		}

//    *
//     *  Indicates the block is part of the transaction.
//     

		internal virtual void add(BlockIo block)
		{
			block.incrementTransactionCount();
			txns[curTxn].Add(block);
		}

//    *
//     *  Commits the transaction to the log file.
//     

		internal virtual void commit()
		{

			//oos.writeObject(txns[curTxn]); Java Version

            

            // c# version 2 with 
			//byte[] txnsBytes = Serialization.SerializeToBytes(txns[curTxn]);
			//oos.Write(txnsBytes);


            bFormatter = new BinaryFormatter();
            ArrayList txnsList = (ArrayList)txns[curTxn];
           // byte[] txnsBytes = Array.ConvertAll(txns[curTxn], b => (byte)b);
            bFormatter.Serialize(fos, txnsList);

           // sbyte[] sbytes = Serialization.serialize(txns[curTxn]);
           // byte[] txnsBytes = Array.ConvertAll(sbytes, b => (byte)b);
          //  fos.Write(txnsBytes, 0, txnsBytes.Length);

            
            
          //  oos.Write(txnsBytes);
          
            fos.Flush(true); 
			sync();

		// set clean flag to indicate blocks have been written to log
			setClean(txns[curTxn]);

		// open a new ObjectOutputStream in order to store
		// newer states of BlockIo
			//oos = new BinaryWriter(fos);
		}

	/// Flushes and syncs 

		private void sync()
		{
            if (oos != null)
            {
                oos.Flush();
            }
            if (fos != null)
            {
                fos.Flush(true);
            }
			//fos.getFD().sync();
		}

//    *
//     *  Shutdowns the transaction manager. Resynchronizes outstanding
//     *  logs.
//     

		internal virtual void shutdown()
		{
			synchronizeLogFromMemory();
			close();
		}

//    *
//     *  Closes open files.
//     

		private void close()
		{
			sync();
			oos.Close();
			fos.Close();
			oos = null;
			fos = null;
		}

//    *
//     * Force closing the file without synchronizing pending transaction data.
//     * Used for testing purposes only.
//     

		internal virtual void forceClose()
		{
			oos.Close();            
			fos.Close();
			oos = null;
			fos = null;
		}

//    *
//     * Use the disk-based transaction log to synchronize the data file.
//     * Outstanding memory logs are discarded because they are believed
//     * to be inconsistent.
//     

		internal virtual void synchronizeLogFromDisk()
		{

          //  close();
           

			for (int i=0; i < _maxTxns; i++)
			{
				if (txns[i] == null) continue;
				
                discardBlocks(txns[i]);
				txns[i] = null;
			}
            close();
			recover();
			open();
		}


//    * INNER CLASS.
//     *  Comparator class for use by the tree set used to store the blocks
//     *  to write for this transaction.  The BlockIo objects are ordered by
//     *  their blockIds.
//     
	  
		
	}
   
	

}